﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

using NngLevelEditor.Utility;
using NngLevelEditor.Manager;

namespace NngLevelEditor.Entities
{
    public class Track : Entity
    {
        private List<Vector2> _ctrlPts;
        private List<Vector2> _dCtrlPts;
        private List<Sprite> _tracks;
        private List<TrackNode> _track;
        private List<float> _us;

        private ContentManager _manager;

        public List<Vector2> ControlPoints
        {
            get { return _ctrlPts; }
        }

        public Track()
        {
            _tracks = new List<Sprite>();
            _us = new List<float>();
            _track = new List<TrackNode>();
        }


        #region Create Spline
        private void UpdateTrack()
        {
            _ctrlPts = new List<Vector2>(_track.Count);
            _dCtrlPts = new List<Vector2>(_track.Count - 1);

            foreach (TrackNode t in _track)
            {
                _ctrlPts.Add(t.Position);
            }

            for (int i = 0; i < _ctrlPts.Count - 1; i++)
            {
                _dCtrlPts.Add(_ctrlPts.Count * (_ctrlPts[i + 1] - _ctrlPts[i]));
            }

            CreateTrack();
        }

        public Vector2 GetPoint(float u)
        {
            return CalcPoint(u, _ctrlPts);
        }

        public Vector2 GetSlope(float u)
        {
            return CalcPoint(u, _dCtrlPts);
        }

        public Sprite GetTrackPiece(int index)
        {
            if (index < _tracks.Count && index >= 0)
                return _tracks[index];
            else
                throw new IndexOutOfRangeException();
        }

        public Sprite GetGreaterTrackPiece(float u)
        {
            for (int i = 0; i < _us.Count; i++)
            {
                if (u > _us[i])
                    return _tracks[i];
            }

            return _tracks[_tracks.Count - 1];
        }

        public Sprite GetLowerTrackPiece(float u)
        {
            for (int i = 0; i < _us.Count; i++)
            {
                if (u < _us[i])
                    return _tracks[i];
            }

            return _tracks[_tracks.Count - 1];
        }

        public int GetTrackIndex(Sprite s)
        {
            return _tracks.IndexOf(s);
        }

        private Vector2 CalcPoint(float u, List<Vector2> pts)
        {
            if (pts.Count > 0)
            {
                Vector2[] points = new Vector2[pts.Count];

                if (u > 1.0f)
                    u = 1.0f;
                else if (u < 0.0f)
                    u = 0.0f;

                for (int i = 0; i < pts.Count; i++)
                {
                    points[i] = pts[i];
                }

                for (int i = 1; i < points.Length; i++)
                {
                    for (int k = 0; k < points.Length - i; k++)
                    {
                        points[k] = (1.0f - u) * points[k] + u * points[k + 1];
                    }
                }

                return points[0];
            }
            return new Vector2();
        }

        private void CreateTrack()
        {
            Sprite s = new Sprite();
            s.LoadContent(_manager, "entities/track");

            float u = 0.0f;
            Vector2 prevPos = this.GetPoint(0.0f);
            Vector2 endPos = this.GetPoint(1.0f);
            Vector2 pos;

            s.Position = prevPos;
            s.RotateTowards(this.GetSlope(u));
            _tracks.Add(s);
            _us.Add(u);

            while (u < 1.0f)
            {
                pos = this.SamplePoint(ref u, s.Scale * 2.0f * 0.98f, prevPos);
                s.Position = pos;
                s.RotateTowards(this.GetSlope(u));
                _tracks.Add(s);
                _us.Add(u);
                prevPos = pos;

                if (Vector2.Distance(prevPos, endPos) < s.Scale * 2.0f * 0.98f)
                {
                    pos = this.GetPoint(1.0f);
                    s.Position = pos + this.GetSlope(u);
                    s.RotateTowards(this.GetSlope(u));
                    _tracks.Add(s);
                    _us.Add(u);
                    return;
                }
            }
        }

        private const float TOLERANCE = 0.1f;

        public Vector2 SamplePoint(ref float u, float dist, Vector2 prevPt)
        {
            float uTop = 1.0f;
            float uPrime = u + (uTop - u) / 2.0f;

            Vector2 pt = this.GetPoint(uPrime);

            while (Vector2.Distance(pt, prevPt) > dist + TOLERANCE || Vector2.Distance(pt, prevPt) < dist - TOLERANCE)
            {
                if (Vector2.Distance(pt, prevPt) > dist + TOLERANCE)
                {
                    uTop = uPrime;
                    uPrime -= (uPrime - u) / 2.0f;
                }
                else
                {
                    uPrime += (uTop - uPrime) / 2.0f;
                }

                pt = this.GetPoint(uPrime);
            }

            u = uPrime;

            return pt;
        }

        #endregion

        #region Track Management

        public void Add(TrackNode tn)
        {
            _track.Add(tn);
            if (_track.Count > 1)
                UpdateTrack();
        }

        public bool MoveUp(TrackNode tn)
        {
            bool result = false;

            try
            {
                int index = _track.IndexOf(tn);

                if (index <= 0)
                {
                    result = false;
                }
                else
                {
                    _track.Remove(tn);
                    _track.Insert(index - 1, tn);
                    result = true;
                    UpdateTrack();
                }
            }
            catch
            {
                result = false;
            }

            return result;
        }

        public bool MoveDown(TrackNode tn)
        {
            bool result = false;

            try
            {
                int index = _track.IndexOf(tn);

                if (index >= _track.Count - 1)
                {
                    result = false;
                }
                else
                {
                    _track.Remove(tn);
                    _track.Insert(index + 1, tn);
                    result = true;
                    UpdateTrack();
                }
            }
            catch
            {
                result = false;
            }

            return result;
        }

        public bool Remove(TrackNode tn)
        {
            try
            {
                _track.Remove(tn);
                ((TrackNode)tn).UnloadContent();
                UpdateTrack();
                return true;
            }
            catch
            {

            }

            return false;
        }

        public void Clear()
        {
            try
            {
                foreach (TrackNode tn in _track)
                {
                    tn.UnloadContent();
                }

                _track.Clear();
            }
            catch
            {

            }
        }

        public override string GetFileString()
        {
            string fileString = "track-\n";

            foreach (TrackNode t in _track)
            {
                fileString += "n " + t.Position.X + " " + t.Position.Y + "\n";
            }
            return fileString + "-track\n";
        }
        #endregion

        public bool DetectSelection(Vector2 clickPosition)
        {
            Vector2 selectionSize = new Vector2(32.0f, 32.0f);
            selectionSize = selectionSize.ToWorldUnits();

            foreach (Entity e in _track)
            {
                Vector2 deltaPosition = e.Position - clickPosition;
                if (Math.Abs(deltaPosition.X) < (selectionSize.X) && Math.Abs(deltaPosition.Y) < (selectionSize.X))
                {
                    SelectionManager.SelectedEntity = e;
                    return true;
                }
            }

            return false;
        }

        //public void Draw(SpriteBatch sb)
        //{
        //    foreach (Sprite s in _tracks)
        //    {
        //        s.Draw(sb);
        //    }
        //}

    }
}
